package com.neusoft.databus.agent.pool.snmp;

import java.io.IOException;

import org.apache.commons.pool2.PooledObject;
import org.snmp4j.CommunityTarget;
import org.snmp4j.Snmp;
import org.snmp4j.Target;
import org.snmp4j.TransportMapping;
import org.snmp4j.UserTarget;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.AuthHMAC128SHA224;
import org.snmp4j.security.AuthHMAC192SHA256;
import org.snmp4j.security.AuthHMAC256SHA384;
import org.snmp4j.security.AuthHMAC384SHA512;
import org.snmp4j.security.AuthMD5;
import org.snmp4j.security.AuthSHA;
import org.snmp4j.security.PrivAES128;
import org.snmp4j.security.PrivAES192;
import org.snmp4j.security.PrivAES256;
import org.snmp4j.security.PrivDES;
import org.snmp4j.security.SecurityModels;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.TSM;
import org.snmp4j.security.USM;
import org.snmp4j.security.UsmUser;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.springframework.stereotype.Component;

import com.neusoft.databus.common.connection.SnmpConnection;
import com.neusoft.databus.common.pool.AbstractPoolFactory;

import lombok.extern.slf4j.Slf4j;

/**
 * snmp连接工厂类
 *
 * @author naxiang
 */
@Slf4j
@Component
public class SnmpFactory extends AbstractPoolFactory<SnmpConnection, SnmpWrapper> {
    private static final String UDP = "udp";

    @Override
    public SnmpWrapper create(final SnmpConnection connection) throws IOException {

        if (SnmpFactory.log.isInfoEnabled()) {
            SnmpFactory.log.info("start to create snmp connection, {}:{}:{}", connection.getIpAddress(),
                    connection.getPort(),
                    connection.getCommunity());
        }

        Snmp snmp = null;
        Target target = null;

        if (connection.getVersion() != SnmpConstants.version3) {
            final Address targetAddress = GenericAddress.parse(connection.getTransportProtocolDependentAddress());
            target = new CommunityTarget();
            ((CommunityTarget) target).setCommunity(new OctetString(connection.getCommunity()));
            target.setAddress(targetAddress);
            target.setRetries(connection.getRetry());
            target.setTimeout(connection.getTimeout() < 100 ? connection.getTimeout() * 1000 : connection.getTimeout());// 超时时间
            target.setVersion(connection.getVersion());
            TransportMapping<?> transport;
            transport = new DefaultUdpTransportMapping();
            snmp = new Snmp(transport);
            transport.listen();
        } else {
            final TransportMapping<?> transport = new DefaultUdpTransportMapping();
            snmp = new Snmp(transport);
            final OctetString localEngineId = new OctetString(MPv3.createLocalEngineID());
            final USM usm = new USM(SecurityProtocols.getInstance(), localEngineId, 0);
            SecurityModels.getInstance().addSecurityModel(usm);
            final OctetString securityName = new OctetString(connection.getUser());

            // 生成连接SNMP v3 设备的用户/密码
            final UsmUser user = new UsmUser(securityName,
                    this.getAuthenticationProtocolOid(connection.getAuthProtocol()),
                    new OctetString(connection.getAuthPass()),
                    this.getPrivacyProtocolOid(connection.getPrivProtocol()),
                    new OctetString(connection.getPrivPass()));

            snmp.getUSM().addUser(securityName, user);
            SecurityModels.getInstance().addSecurityModel(new TSM(localEngineId, false));

            target = new UserTarget();
            target.setSecurityLevel(Integer.valueOf(connection.getLevel()));
            target.setSecurityName(securityName);
            target.setAddress(GenericAddress
                    .parse(SnmpFactory.UDP + ":" + connection.getIpAddress() + "/" + connection.getPort()));
            target.setVersion(SnmpConstants.version3);// SNMP
            target.setRetries(connection.getRetry());//
            target.setTimeout(connection.getTimeout() < 100 ? connection.getTimeout() * 1000 : connection.getTimeout());// 超时时间

            transport.listen();
        }
        final SnmpWrapper warpper = new SnmpWrapper(snmp, target);
        return warpper;
    }

    @Override
    public boolean validateObject(final SnmpConnection key, final PooledObject<SnmpWrapper> p) {
        return true;
    }

    @Override
    public void destroy(final SnmpWrapper v) {

        if (SnmpFactory.log.isInfoEnabled()) {
            SnmpFactory.log.info("destroy snmp connection object from pool，{}", v.getTarget().getAddress());
        }
        final Snmp snmp = v.getSnmp();
        try {
            snmp.close();
        } catch (final IOException e) {
        }
    }

    /**
     * 获取snmpv3加密方式 默认md5
     *
     * @param authenticationProtocol
     * @return
     */
    private OID getAuthenticationProtocolOid(final String authenticationProtocol) {
        OID oid = AuthMD5.ID;
        switch (authenticationProtocol.toLowerCase()) {
        case "sha":
            oid = AuthSHA.ID;
            break;
        case "sha224":
            oid = AuthHMAC128SHA224.ID;
            break;
        case "sha256":
            oid = AuthHMAC192SHA256.ID;
            break;
        case "sha384":
            oid = AuthHMAC256SHA384.ID;
            break;
        case "sha512":
            oid = AuthHMAC384SHA512.ID;
            break;
        default:
            break;
        }
        return oid;
    }

    /**
     * 获取snmpv3鉴权方式 默认des
     *
     * @param privacyProtocol
     * @return
     */
    private OID getPrivacyProtocolOid(final String privacyProtocol) {
        OID oid = PrivDES.ID;
        switch (privacyProtocol.toLowerCase()) {
        case "aes128":
            oid = PrivAES128.ID;
            break;
        case "aes192":
            oid = PrivAES192.ID;
            break;
        case "aes256":
            oid = PrivAES256.ID;
            break;
        default:
            break;
        }
        return oid;
    }
}
